Skip to main content

2. Introdução ao PhaserJS

 PhaserJS é um framework poderoso para a criação de jogos em HTML5. Ele fornece uma estrutura simples e eficiente para desenvolver jogos interativos diretamente no navegador.

 Neste guia, abordaremos as funções básicas do PhaserJS, incluindo as etapas principais para carregar recursos, criar cenas e atualizar elementos no jogo.


1. Estrutura Básica de um Jogo no PhaserJS

 Um jogo no Phaser segue um ciclo padrão que envolve três funções principais:

  • preload(): Carrega os recursos (imagens, áudios, sprites, etc.).
  • create(): Configura os objetos na cena.
  • update(): Atualiza o jogo em tempo real.

 A estrutura básica de um jogo no PhaserJS é a seguinte:

const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update
}
};

const game = new Phaser.Game(config);

function preload() {
// Carregar imagens e outros recursos aqui
}

function create() {
// Criar elementos na tela
}

function update() {
// Atualizar elementos a cada frame
}

2. Adicionando Imagens na Tela

 Para adicionar imagens ao jogo, primeiro carregamos os recursos na função preload() e depois os exibimos na função create().

 Exemplo:

function preload() {
this.load.image('player', 'assets/player.png');
}

function create() {
this.add.image(400, 300, 'player');
}

 No exemplo acima, carregamos uma imagem chamada player.png e depois a exibimos no centro da tela.


3. Mecânica de Movimentação

 Para movimentar um objeto, podemos usar o teclado e atualizar sua posição na função update().

 Exemplo de movimentação com setas do teclado:

let player;
let cursors;

function preload() {
this.load.image('player', 'assets/player.png');
}

function create() {
player = this.physics.add.sprite(400, 300, 'player');
cursors = this.input.keyboard.createCursorKeys();
}

function update() {
if (cursors.left.isDown) {
player.setVelocityX(-160);
} else if (cursors.right.isDown) {
player.setVelocityX(160);
} else {
player.setVelocityX(0);
}

if (cursors.up.isDown) {
player.setVelocityY(-160);
} else if (cursors.down.isDown) {
player.setVelocityY(160);
} else {
player.setVelocityY(0);
}
}

 Neste exemplo:

  • Criamos um sprite player e ativamos física nele.
  • Definimos cursors para capturar entradas do teclado.
  • No update(), ajustamos a velocidade do player conforme as teclas pressionadas.

Ponto importante!!!

 Quando se trata de movimentação, é possível criar diferentes lógicas na programação de acordo com as suas necessidades. O código acima permite que você mova o "player" em diagonalmente, mas caso você não queira que isso aconteça, um exemplo abaixo seria uma das lógicas que poderiam ser implementadas:

let lastKey = null; // Armazena a última tecla pressionada

function update() {
let velocityX = 0;
let velocityY = 0;

if (cursors.left.isDown || cursors.right.isDown || cursors.up.isDown || cursors.down.isDown) {
// Define a última tecla pressionada
if (cursors.left.isDown) {
lastKey = 'left';
} else if (cursors.right.isDown) {
lastKey = 'right';
} else if (cursors.up.isDown) {
lastKey = 'up';
} else if (cursors.down.isDown) {
lastKey = 'down';
}
}

// Move apenas no eixo correspondente à última tecla pressionada
if (lastKey === 'left') {
velocityX = -160;
} else if (lastKey === 'right') {
velocityX = 160;
} else if (lastKey === 'up') {
velocityY = -160;
} else if (lastKey === 'down') {
velocityY = 160;
}

// Se nenhuma tecla estiver pressionada, reseta o lastKey
if (!cursors.left.isDown && !cursors.right.isDown && !cursors.up.isDown && !cursors.down.isDown) {
lastKey = null;
}

player.setVelocityX(velocityX);
player.setVelocityY(velocityY);
}

4. Definindo Hitbox no PhaserJS

 A hitbox define a área de colisão do objeto, sendo essencial para detectar interações no jogo.

 Para modificar a hitbox de um sprite, podemos usar setSize() e setOffset():

function create() {
player = this.physics.add.sprite(400, 300, 'player');
player.setSize(32, 48); // Define uma hitbox menor
player.setOffset(16, 24); // Ajusta a posição da hitbox dentro do sprite
}

 Isso é útil quando a imagem do sprite tem espaços vazios ao redor do personagem ou quando queremos uma área de colisão mais precisa.

5. Modo Debug da Hitbox

 Para visualizar a hitbox e depurar colisões, podemos ativar o modo debug do sistema de física.

const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: true // Habilita a exibição das hitboxes
}
},
scene: {
preload: preload,
create: create,
update: update
}
};

 Com essa configuração, todas as hitboxes dos objetos com física serão desenhadas na tela, facilitando ajustes e correções.

 Se quiser habilitar o debug apenas para um objeto específico, você pode adicionar:

this.physics.world.createDebugGraphic();

 Isso irá mostrar apenas as hitboxes dos elementos ativos no sistema de física, tornando a depuração mais precisa.

6. Mecânica de colisão

Dento do Phaser existem alguns tipos de colisão, sendo eles: O Colider e o Overlap

6.1. O que é Colisão em um Jogo?

Colisão em um jogo é o evento que ocorre quando dois ou mais objetos dentro do ambiente virtual se tocam ou se sobrepõem. Esse conceito é fundamental em jogos digitais, pois define como os elementos interagem entre si, como personagens, inimigos, itens coletáveis e cenários.

6.2. Tipos de Colisão

Existem diferentes formas de tratar colisões em jogos, dependendo da mecânica desejada:

  1. Colisão Física (Realista)

    • Usa motores de física para calcular impactos, força e direção após a colisão.
    • Exemplo: Em um jogo de sinuca, quando uma bola bate em outra e muda sua trajetória.
  2. Colisão Simples (Arcade)

    • Apenas impede que objetos atravessem uns aos outros, sem simular física realista.
    • Exemplo: Um personagem que não pode atravessar paredes em um jogo de plataforma.
  3. Sobreposição (Overlap)

    • Detecta se dois objetos estão no mesmo espaço, mas sem impedir a passagem.
    • Exemplo: Coletar uma moeda sem que o personagem precise colidir fisicamente com ela.

6.3. Como a Colisão é Detectada?

Os jogos usam diferentes técnicas para identificar colisões, como:

  • Hitbox: Área invisível ao redor do objeto que define quando a colisão ocorre. Pode ser um retângulo, círculo ou formato mais complexo.
  • Pixel Perfect: Verifica se os pixels de dois objetos realmente se sobrepõem. É mais preciso, mas consome mais recursos.
  • Sistemas de Física: Motores como Box2D e Matter.js simulam colisões realistas usando cálculos de massa, velocidade e força.

6.4. Exemplos de Colisão em Jogos

  • Jogos de plataforma: Um personagem colide com o chão e não atravessa ele.
  • Jogos de corrida: Carros colidem uns com os outros e são empurrados.
  • Jogos de tiro: Balas colidem com inimigos e causam dano.

7. Colider:

O Collider verifica as hitboxes dos dois objetos mencionados na função e impede que eles ocupem o mesmo espaço. No entanto, em alguns casos, seu uso pode não ser recomendado, pois a colisão pode fazer com que os objetos troquem inércia, o que pode gerar interações indesejadas.

Isso é especialmente problemático em objetos como itens coletáveis, que podem ser empurrados involuntariamente pelo jogador, ou em situações onde queremos que a colisão ocorra sem afetar a física do objeto, como ao atravessar uma porta ou ativar um interruptor.

this.physics.add.collider(X, Y, Z);

O método this.physics.add.collider(X, Y, Z) em Phaser é usado para adicionar uma colisão entre dois objetos, X e Y. Quando esses dois objetos se tocarem ou se sobrepuserem, a física do jogo vai gerenciar a colisão automaticamente.

Aqui está o que cada parte faz:

  1. X e Y: São os dois objetos (ou grupos de objetos) que você deseja que colidam. Eles podem ser sprites, corpos físicos (como objetos com arcade.physics), ou grupos de objetos.

  2. Colisão automática: Diferente do método overlap, que verifica sobreposição sem interações físicas diretas, collider lida com interações físicas reais. Por exemplo, se os objetos forem sprites com corpos de física, a colisão pode causar um empurrão ou impacto, com base nas configurações dos corpos físicos (massa, elasticidade, etc.).

  3. Z: É a função que vai ser chamada quando ocorrer uma colisão entre X e Y. Essa função pode ser utilizada para executar ações específicas após a colisão, como emitir um som, trocar a animação dos objetos, aplicar dano, alterar o estado do jogo (por exemplo, diminuir a vida do jogador ou destruir um inimigo), ou até mesmo iniciar um efeito visual.

Observação: É possivel criar uma colisão sem definir uma função a ser chamada, sendo possivel realizar essa colisão dessa forma: this.physics.add.collider(X, Y)

7.1. Bounce e o efeito da colisão

Quando dois objetos colidem, eles normalmente param, mas podemos configurar um comportamento mais dinâmico usando a propriedade bounce. O bounce define o quanto um objeto "quica" ao colidir com outro.

No Arcade Physics, podemos ajustar esse efeito com setBounce(value), onde value pode variar de 0 (sem quique) até 1 (quique total, como uma bola de pingue-pongue).

7.2. Exemplo de uso do bounce:

player.setBounce(0.2); // O jogador terá um leve quique ao tocar no chão
bola.setBounce(1); // A bola quicará indefinidamente ao bater nas paredes

Se quisermos que um grupo inteiro tenha esse efeito:

let bolas = this.physics.add.group({
key: 'ball',
repeat: 5,
setXY: { x: 100, y: 100, stepX: 150 }
});

// Aplicando bounce para todas as bolas do grupo
bolas.children.iterate(ball => {
ball.setBounce(1);
});

7.3. Bounce e o efeito da colisão

Quando dois objetos colidem, o comportamento deles depende do tipo de corpo físico que possuem:

  • Se um dos objetos for estático (setStatic(true)), o outro objeto será impedido de atravessá-lo e parará, a menos que tenha bounce configurado.

Dica: Se estiver criando um chão, plataformas ou paredes, lembre-se de definir esses objetos como estáticos (setStatic(true)), para evitar que saiam se movendo ao colidir com outros elementos.

  • Se ambos forem dinâmicos, a colisão pode fazer com que troquem inércia e se movimentem conforme suas propriedades físicas, como massa e velocidade.

7.4. Quando usar o Collider?

O Collider é útil em diversos cenários, como:

  • Evitar que objetos atravesse o chão e plataformas:
    this.physics.add.collider(object, platforms);
  • Criar colisões entre o jogador e obstáculos que devem bloqueá-lo:
    this.physics.add.collider(player, wall);
  • Fazer com que caixas empilhadas interajam fisicamente entre si:
    this.physics.add.collider(boxes, boxes);

7.5. Quando não usar o Collider?

  • Em itens coletáveis, que devem ser sobrepostos e não bloqueados:
    this.physics.add.overlap(player, coins, collectCoin, null, this);
  • Em portas ou botões, onde basta detectar a interação sem impedir o movimento:
    this.physics.add.overlap(player, door, enterDoor, null, this);

8. Overlap:

Diferente do Collider, que impede a sobreposição de objetos, o overlap apenas detecta quando dois objetos se encontram, sem aplicar nenhuma física sobre eles. Isso é útil quando queremos que um objeto reaja ao contato sem bloqueá-lo.
A função Overlap, possue algumas entradas

this.physics.add.overlap(X,Y,Z, null, this);
  1. X e Y: São os dois objetos que você está verificando a sobreposição. Esses podem ser sprites, grupos de sprites ou qualquer outro objeto físico que você tenha adicionado à física do jogo.

  2. Z: Esse é o callback (função) que será chamado quando a colisão entre X e Y ocorrer. Ou seja, quando houver sobreposição entre os dois objetos, a função Z será executada.

  3. null: Aqui, você tem um valor que pode ser um critério de filtragem adicional. Pode ser um valor que ajude a determinar se a colisão deve ser considerada ou não. Quando você passa null, significa que você não está usando nenhum filtro extra e que a sobreposição será verificada sem condições adicionais.

  4. this: Este é o contexto (this) da função de callback, ou seja, o escopo em que a função será executada. Geralmente, this é o objeto da cena do Phaser (como a instância da cena que está sendo carregada), garantindo que você tenha acesso a propriedades e métodos dessa cena dentro da função de callback.

8.1. Exemplos de uso do Overlap

  • Coletar itens sem que o jogador os empurre:
    this.physics.add.overlap(player, coins, collectCoin, null, this);
    function collectCoin(player, coin) {
    coin.disableBody(true, true); // Faz a moeda desaparecer
    }
  • Ativar um botão ou interruptor ao tocar nele:
    this.physics.add.overlap(player, button, activateSwitch, null, this);
    function activateSwitch(player, button) {
    console.log("Interruptor ativado!");
    }
  • Detectar quando o jogador entra em uma área específica, como uma porta ou checkpoint:
    this.physics.add.overlap(player, door, enterDoor, null, this);
    function enterDoor(player, door) {
    console.log("Jogador entrou na porta!");
    }

Curiosidade: O overlap é mais leve computacionalmente do que o Collider, pois não precisa calcular a separação dos objetos ou lidar com forças físicas.

9 Destruir Objetos

Em alguns casos, queremos que um objeto desapareça do jogo após uma determinada interação, como quando um inimigo é derrotado, um item coletável é recolhido ou um projétil atinge um alvo. No Phaser, podemos fazer isso com o método destroy().

objeto.destroy();

Esse método remove o objeto da cena e libera a memória associada a ele, garantindo que ele não continue processando eventos ou colisões.

9.1. Exemplos de uso de destroy()

Coletar itens e removê-los do jogo:

this.physics.add.overlap(player, coin, collectCoin, null, this);

function collectCoin(player, coin) {
coin.destroy(); // Remove a moeda da cena ao ser coletada
}

Remover um inimigo ao ser derrotado:

this.physics.add.collider(player, enemy, defeatEnemy, null, this);

function defeatEnemy(player, enemy) {
enemy.destroy(); // Remove o inimigo ao colidir com o jogador
}

Fazer um projétil desaparecer ao colidir com uma parede:

this.physics.add.collider(bullet, wall, destroyBullet, null, this);

function destroyBullet(bullet, wall) {
bullet.destroy(); // Remove a bala ao atingir a parede
}

Atenção: Quando um objeto é destruído, qualquer referência a ele se torna inválida. Se tentar acessá-lo depois de destruído, o jogo pode apresentar erros.


10. Explicação do this

No Phaser, a palavra-chave this é usada para se referir ao contexto atual da cena. Isso é importante porque cada cena do jogo é uma classe que contém seus próprios métodos e propriedades.

Quando usamos this dentro de uma função, estamos acessando elementos da cena, como os objetos do jogo, grupos de física, animações e sons.

10.1. Exemplo de uso do this

10.1.1. Criando e acessando objetos na cena:

this.player = this.physics.add.sprite(100, 200, 'player');

Aqui, this.player armazena o sprite do jogador na cena atual, permitindo que seja referenciado depois.

10.1.2. Usando this dentro de funções:

Ao passar this como último argumento em collider ou overlap, garantimos que a função de callback tenha acesso às propriedades da cena.

this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this);

Isso permite que a função collectCoin acesse outros elementos da cena sem perder o contexto.

10.1.3. Sem this, o código pode falhar!

Se tentarmos usar uma função sem passar this corretamente, podemos perder o acesso aos elementos da cena:

this.physics.add.collider(this.player, this.enemy, defeatEnemy);

function defeatEnemy(player, enemy) {
this.enemy.destroy(); // ERRO: `this` não está definido aqui!
}

Para corrigir isso, basta passar this como último argumento:

this.physics.add.collider(this.player, this.enemy, defeatEnemy, null, this);

Dessa forma, this.enemy funcionará corretamente dentro da função defeatEnemy.

10.2. Resumo sobre this

  • this refere-se ao contexto da cena atual.
  • this é necessário para acessar objetos e métodos da cena.
  • Sempre passe this como último argumento em funções de collider e overlap.
  • Se esquecer de usar this, pode acabar com erros ao tentar acessar elementos da cena!

Dica: Para evitar confusão, use funções de seta (=>) dentro de eventos sempre que possível, pois elas mantêm automaticamente o contexto de this:

this.physics.add.collider(this.player, this.enemy, (player, enemy) => {
enemy.destroy(); // Funciona sem precisar passar `this` manualmente
});